#include "TerrainManager.h"

void read_callback( png_structp png_ptr, png_bytep to_lib, png_size_t lenght ) {
	FILE *file = (FILE*)png_ptr->io_ptr;
	fread( to_lib, 1, lenght, file );
}

TerrainManager::TerrainManager( OBJHANDLE _obj, vPlanet *_OBJ ) {
	obj = NULL;
	OBJ = NULL;
	objname = NULL;

	rend = NULL;
	det = NULL;

	L8HeightMapGenerated = false;
	HeightScale = 1.0f;
	FlatDispMicroTexData = CoarseDispMicroTexData = NULL;
	DisplacementMicroTexDim = 0.0f;
	texbuf = specbuf = normbuf = heightbuf = lcoverbuf = NULL;
	ntex = nspec = nnorm = nheight = nlcover = nhitex = nhispec = nhinorm = nhiheight = nhilcover = 0;
	tex_fname = spec_fname = norm_fname = height_fname = lcover_fname = NULL;
	memset( tex_fname_month, 0, sizeof(char*)*12 );
	memset( &RP, 0, sizeof(RENDERPARAMS) );
	
	//==========================================================================
	//		Get planet configuration
	//==========================================================================

	char tmp[128];
	obj = _obj;//orbiter handle
	OBJ = _OBJ;//D3D11Client handle
	oapiGetObjectName( obj, tmp, 128 );
	objname = new char [strlen(tmp)+1];
	strcpy( objname, tmp );
//	bEarth = (strncmp( objname, "Earth", 5 ) == 0);

	CFG = &OBJ->CFG;
	maxlvl = CFG->maxTileResolution;
	maxbaselvl = min( 8, maxlvl );
	//==========================================================================
	//		Find and load available textures
	//==========================================================================

	TILEMESH1 **TPL;
	rend = new TileRenderMgr( this, CFG, &TPL );
//	det = new DetailsManager( this, CFG );

	//L1-L8 tiles
	tiledesc = new TILEDESC1 [patchidx[maxbaselvl]];
	memset( tiledesc, 0, sizeof(TILEDESC1)*patchidx[maxbaselvl] );

	//init L9-L18 tile buffer
	/*buf = NULL;
	nbuf = bsize = 0;*/

	FindResources();
	LoadTextures();
	LoadSpecMask();
	LoadNormalMap();
	LoadHeightMap();
	LoadLandCoverMap();

	if( DisplacementMicroTexDim = rend->InitResources( &FlatDispMicroTexData, &CoarseDispMicroTexData, _OBJ->GetScene()->GetClient() ) == 0.0f ) 
	{
		CFG->DisplacementMicroTex_mult = 1.0f;
		DisplacementMicroTexDim = 65.0f;
	}

//	det->InitResources();

	//==========================================================================
	//		write correct BS positions and redius to L1-L7 tiledesc
	//==========================================================================

	tiledesc[0].bsPos = TPL[1][0].bsPos;
	tiledesc[1].bsPos = TPL[2][0].bsPos;
	tiledesc[2].bsPos = TPL[3][0].bsPos;
	tiledesc[3].bsPos = TPL[4][0].bsPos;
	tiledesc[4].bsPos = TPL[4][1].bsPos;

	tiledesc[0].bsRad = TPL[1][0].bsRad;
	tiledesc[1].bsRad = TPL[2][0].bsRad;
	tiledesc[2].bsRad = TPL[3][0].bsRad;
	tiledesc[3].bsRad = TPL[4][0].bsRad;
	tiledesc[4].bsRad = TPL[4][1].bsRad;

	UINT hemisphere, idx, lvl, ilng;
	INT ilat;
	for( lvl = idx = 5; lvl <= maxbaselvl && lvl < 8; lvl++ ) {
		for( hemisphere = 0; hemisphere < 2; hemisphere++ ) {
			for( ilat = NLAT[lvl] - 1; ilat >= 0; ilat-- ) {
				for( ilng = 0; ilng < NLNG[lvl][ilat]; ilng++ ) {
					tiledesc[idx].bsPos = TPL[lvl][ilat].bsPos;
					tiledesc[idx].bsRad = TPL[lvl][ilat].bsRad;
					idx++;
				}
			}
		}
	}

	//==========================================================================
	//		Heightmapping and details params
	//==========================================================================

	for( UINT j = patchidx[7]; j < patchidx[maxbaselvl]; j++ )
		tiledesc[j].flags |= RECOMPUTEBS;

	for( UINT j = 0; j < buf.size(); j++ )
		buf[j]->flags |= RECOMPUTEBS;

	tiles_noculling.reserve( 100 );
	tiles_shadows.reserve( 100 );
	tiles_camera.reserve( 100 );
}

TerrainManager::~TerrainManager() {
	DWORD j;

	delete rend;
//	delete det;

	//texture filenames
	if( objname )		delete [ ] objname;
	if( tex_fname )		delete [ ] tex_fname;
	if( spec_fname )	delete [ ] spec_fname;
	if( norm_fname )	delete [ ] norm_fname;
	if( height_fname )	delete [ ] height_fname;
	if( lcover_fname )	delete [ ] lcover_fname;
/*
	if( bMonthTextures ) {
		for( UINT8 j = 0; j < 12; j++ )
			if( tex_fname_month[j] )
				delete [ ] tex_fname_month[j];
	}
*/
	//tiles
	if( tiledesc )
		delete [ ] tiledesc;

	//textures
	/*if( texbuf ) {
		for( j = 0; j < ntex; j++ )		REL( texbuf[j] );
		delete [ ] texbuf;
	}*/

	//spec/emissive
	if( specbuf ) {
		for( j = 0; j < nspec; j++ )	REL( specbuf[j] );
		delete [ ] specbuf;
	}

	//normal
	/*if( normbuf ) {
		for( j = 0; j < nnorm; j++ )	REL( normbuf[j] );
		delete [ ] normbuf;
	}*/

	//height
	/*if( heightbuf ) {
		for( j = 0; j < nheight; j++ )	REL( heightbuf[j] );
		delete [ ] heightbuf;
	}*/
	
	//LCC
	if( lcoverbuf ) {
		for( j = 0; j < nlcover; j++ )		REL( lcoverbuf[j] );
		delete [ ] lcoverbuf;
	}

	if( buf.size() ) {
		for( int j = 0; j < buf.size(); j++ ) {
			if( !(buf[j]->flags & LOAD_TEX) ) {
				//REL( buf[j]->tex );
				REL( buf[j]->ltex );
			}
			if( !(buf[j]->flags & LOAD_NORM) )
			{
				//REL( buf[j]->ntex );
			}
			if( !(buf[j]->flags & LOAD_HEIGHT) )
			{
				//REL( buf[j]->htex );
				if (buf[j]->HMAP)
				{
					delete [] buf[j]->HMAP;
				}
			}
			if( !(buf[j]->flags & LOAD_LCOVER) )
				REL( buf[j]->ctex );
			delete buf[j];
		}
		buf.clear();
		//delete [ ] buf;
	}
}

//#pragma region resource loading

#pragma region config

void TerrainManager::FindResources() {
	char line[256], path[256];	

	oapiWriteLogV("Searching resources...");

	//find tile textures
	sprintf( line, "%s_tile.tex", objname );
	if( gc->TexturePath( line, path ) ) {
		tex_fname = new char [strlen(path)+1];
		strcpy( tex_fname, path );
		oapiWriteLogV("Found texture file %s", tex_fname);
	}
/*	
	if( bEarth ) {
	//	Month = GetMonth( oapiGetSimMJD() );

		if( bMonthTextures ) {
			bMonthTextures = false;
			for( UINT8 j = 0; j < 12; j++ ) {
				bMonth[j] = false;
				tex_fname_month[j] = NULL;
				//L1-L8
				sprintf( line, "%s_%d.tex", j+1 );
				if( gc->TexturePath( line, path ) ) {
					//L9-LMAX
					sprintf( line, "%s_tile_%d.tex", j+1 );
					if( gc->TexturePath( line, path ) ) {
						gMonthTextures = true;
						bMonth[j] = true;
						tex_fname_month[j] = new char [strlen(path)+1];
						strcpy( tex_fname_month[j], path );
					}
				}
			}
		}

		if( bMonthTextures ) {
			for( UINT8 j = 0; j < 12; j++ ) {
				if( bMonth[j] ) {
					MonthTexIdx = j;
					break;
				}
			}
		}
	}
*/
	//specular and emissive
	sprintf( line, "%s_tile_lmask.tex", objname );
	if( gc->TexturePath( line, path ) ) {
		spec_fname = new char [strlen(path)+1];
		strcpy( spec_fname, path );
		oapiWriteLogV("Found specular file %s", spec_fname);
	}

	//normal map
	sprintf( line, "%s_tile_norm.tex", objname );
	if( gc->TexturePath( line, path ) ) {
		norm_fname = new char [strlen(path)+1];
		strcpy( norm_fname, path );
		oapiWriteLogV("Found normal map file %s", norm_fname);
	}

	//heightmap
	sprintf( line, "%s_height.tex", objname );
	if( gc->TexturePath( line, path ) ) {
		height_fname = new char [strlen(path)+1];
		strcpy( height_fname, path );
		oapiWriteLogV("Found height map file %s", height_fname);
//		bHasHeightmapFile = true;
	}
	else {
		//there's no heightmap file
		//create generation module
		return;
		if( CFG->bHeightMapGenerator ) {
		/*	HeightMapGenModule = new noise::module::Perlin;

			noise::module::Perlin *perlin = (noise::module::Perlin*)HeightMapGenModule;

			perlin->SetOctaveCount( 10 );
			perlin->SetFrequency( 3.0 );
			perlin->SetPersistence( 0.72 );*/
		
		/*	GenModule = new noise::module::RidgedMulti;

			noise::module::RidgedMulti *ridge = (noise::module::RidgedMulti*)GenModule;
			ridge->SetFrequency( 30.0 );
			ridge->SetLacunarity( 3.5 );//1.5-3.5
			ridge->SetOctaveCount( 10 );*/

		/*	HeightMapGenModule = new noise::module::Voronoi;
			noise::module::Voronoi *voronoi = (noise::module::Voronoi*)HeightMapGenModule;
			voronoi->EnableDistance( true );
			voronoi->SetFrequency( 30.0 );
			voronoi->SetDisplacement( 2.5 );*/
	
		/*	HeightMapGenModule = new noise::module::Billow;
			noise::module::Billow *billow = (noise::module::Billow*)HeightMapGenModule;
			billow->SetFrequency( 15.0 );
			billow->SetLacunarity( 2.0 );
			billow->SetOctaveCount( 6 );
			billow->SetPersistence( 0.72 );*/

/*			HeightMapGenModule = new noise::module::Spheres;
			noise::module::Spheres *sphere = (noise::module::Spheres*)HeightMapGenModule;
			sphere->SetFrequency( 30.0 );*/

			VECTOR3 gpos, cpos;

			//get distance between object's surface and camera
			oapiCameraGlobalPos( &cpos );
			oapiGetGlobalPos( obj, &gpos );
			gpos -= cpos;
			double _dist = length( gpos );
			_dist -= OBJ->rad;

			//if camera is close enough generate heightmap
		/*	if( _dist/OBJ->rad < 10.0 ) {
				tgen->GenerateL5Heightmap( this, CFG->minGenAltitude, CFG->maxGenAltitude );
				L8HeightMapGenerated = true;
			}*/
		}
	}

	//LCC map
	sprintf( line, "%s_lcover.tex", objname );
	if( gc->TexturePath( line, path ) ) {
		lcover_fname = new char [strlen(path)+1];
		strcpy( lcover_fname, path );
	}
}

#pragma endregion config file parser and resource finder
//========================================================
//					Diffuse textures
//========================================================

#pragma region resource loading

void TerrainManager::LoadTextures() {
	//loads L1-L8 textures and adds subtile information in L9-Lmax tiles

	//load Planet.tex
	//read Planet_tile.bin
	//add subtile info
	DWORD j, i;
	char fname[256], cpath[256];

	//loading Planet.tex with L1-L8 textures
	ntex = patchidx[maxbaselvl];
	texbuf = new ID3D11ShaderResourceView *[ntex];

//	if( bMonthTextures )		sprintf( fname, "%s_%d.tex", objname, MonthTexIdx + 1 );
//	else
	sprintf( fname, "%s.tex", objname );

	if( gc->TexturePath( fname, cpath ) ) {
		if( ntex = LoadTextures( cpath, ntex, texbuf ) ) {
			while( ntex < patchidx[maxbaselvl] )	maxlvl = --maxbaselvl;
			while( ntex > patchidx[maxbaselvl] )	REL( texbuf[--ntex] );
			for( j = 0; j < patchidx[maxbaselvl]; j++ )
				tiledesc[j].tex.reset(new CachedTexture(texbuf[j]));
		}
		else {
			delete [ ] texbuf;
			texbuf = NULL;
		}
	}

	//reading Planet_tile.bin and adding subtile information to L8-LMAX tiles
	if( maxlvl > 8 ) {
		FILE *file;
		sprintf( fname, "%s_tile.bin", objname );
		if( gc->TexturePath( fname, cpath ) && (file = fopen( cpath, "rb" )) ) {
			char id[9];

			memset( id, 0, 9 );
			fread( id, 1, 8, file );
			DWORD tilever = 10;
			if( !strncmp( id, "PLTS", 4 ) )
				tilever = 1;
			else {
				tilever = 0;
				fseek( file, 0, SEEK_SET );
			}

			DWORD n;
			fread( &n, 4, 1, file );
			TILEFILESPEC *tfs = new TILEFILESPEC [n];
			fread( tfs, sizeof(TILEFILESPEC), n, file );
			fclose( file );

			//convert old indexes to new 

			TILEDESC1 *tile8 = tiledesc + patchidx[7];//first L8 tile
			for( j = 0; j < 364; j++ ) {
				TILEDESC1 &tile8j = tile8[j];
				for( i = 0; i < 4; i++ )
					if( tfs[j].subidx[i] )
						AddSubTileData( tile8j, tfs, j, i, 9, tilever );
			}

			delete [ ] tfs;
		}
	}
}

void TerrainManager::AddSubTileData( TILEDESC1 &td, TILEFILESPEC *tfs, DWORD idx, DWORD sub, DWORD level, DWORD tilever ) {
	DWORD j, subidx = tfs[idx].subidx[sub];
	TILEFILESPEC &t = tfs[subidx];
	bool bSubTiles = false;

	for( j = 0; j < 4; j++ ) {
		if( t.subidx[j] ) {
			bSubTiles = true;
			break;
		}
	}

	if( t.flags || bSubTiles ) {
		if( level <= maxlvl ) {
			td.subtile[sub] = NewTile( 0.0f, 0.0f );	//create new tiledesc

			td.subtile[sub]->flags |= t.flags;			//get flags from file data
			//texture offset
			td.subtile[sub]->tex.reset(new CachedTexture(td.subtile[sub], LOAD_TEX, t.sidx));//write texture offset or index
			//specmask texture offset
			if( CFG->bSpecularMap ) {
				if( t.midx != NOTILE )
					td.subtile[sub]->ltex = (ID3D11ShaderResourceView*)t.midx;
			}
			else
				td.subtile[sub]->flags |= 1;//"no specular mask flag"

			td.subtile[sub]->flags |= RECOMPUTEBS;
			td.subtile[sub]->flags |= LOAD_TEX;				//"not loaded"
			if( !tilever )	td.subtile[sub]->flags |= OLDIDX;	//"old style index"
			//calls the same function for every existing subtile
			if( bSubTiles )
				for( j = 0; j < 4; j++ )
					if( t.subidx[j] )
						AddSubTileData( *td.subtile[sub], tfs, subidx, j, level+1, tilever );
			//increment hi-res texture counters
			nhitex++;
			if( t.midx != NOTILE )	nhispec++;
		}
		else	td.subtile[sub] = NULL;	//maximum level reached
	}
}

//========================================================
//					Specular textures
//========================================================

void TerrainManager::LoadSpecMask() {
	//load Planet_lmask.bin
	//load Planet_lmask.tex
	FILE *file;
	DWORD j;
	char fname[256], cpath[256];

	if( !CFG->bSpecularMap )	return;

	//read Planet_lmask.bin
	sprintf( fname, "%s_lmask.bin", objname );
	if( CFG->bSpecularMap && gc->TexturePath( fname, cpath ) && (file = fopen( cpath, "rb" )) ) {
		BYTE minres, maxres, flag;
		WORD *tflag = NULL;
		DWORD npatch, idx;
		LMASKFILEHEADER lmfh;

		fread( &lmfh, sizeof(lmfh), 1, file );
		if( !strncmp( lmfh.id, "PLTA0100", 8 ) ) {
			minres = lmfh.minres;
			maxres = lmfh.maxres;
			npatch = lmfh.npatch;
			tflag = new WORD [npatch];
			fread( tflag, 2, npatch, file );
		}
		else {
			fseek( file, 0, SEEK_SET );
			fread( &minres, 1, 1, file );
			fread( &maxres, 1, 1, file );
			npatch = patchidx[maxres] - patchidx[minres-1];
			tflag = new WORD [npatch];
			for( j = 0; j < npatch; j++ ) {
				fread( &flag, 1, 1, file );
				tflag[j] = flag;
			}
		}
		fclose( file );

		for( j = idx = 0; j < patchidx[maxbaselvl]; j++ ) {
			if( j < patchidx[minres-1] )
				tiledesc[j].flags |= 1;
			else {
				tiledesc[j].flags |= tflag[idx++];
				if( ((tiledesc[j].flags & 3) == 3 ) || (tiledesc[j].flags & 4 ) )
					nspec++;
			}
		}

		delete [ ] tflag;

		if( nspec ) {
			//load Planet_tile.tex
			sprintf( fname, "%s_lmask.tex", objname );			
			if( gc->TexturePath( fname, cpath ) ) {
				specbuf = new ID3D11ShaderResourceView *[nspec];
				DWORD n = nspec;
				if( n = LoadTextures( cpath, nspec, specbuf ) ) {
					if( n < nspec ) {	//not all masks was loaded
						//delete all specular masks and set everything to NULL
						for( j = 0; j < n; j++ )
							REL( specbuf[j] );
						delete [ ] specbuf;
						specbuf = NULL;
						nspec = 0;
						for( j = 0; j < patchidx[maxbaselvl]; j++ )
							tiledesc[j].flags |= 1;
					}
					else {	//everything was OK
						for( j = n = 0; j < patchidx[maxbaselvl]; j++ ) {
							if( ((tiledesc[j].flags & 3) == 3) || (tiledesc[j].flags & 4) ) {
								if( n < nspec )
									tiledesc[j].ltex = specbuf[n++];
								else
									tiledesc[j].flags |= 1;
							}
							if( !CFG->bSpecularMap ) {
								tiledesc[j].flags &= 0x7FC;
								tiledesc[j].flags |= 1;
							}
						}
					}
				}
				else {
					delete specbuf;
					specbuf = NULL;
					nspec = 0;
					for( j = 0; j < patchidx[maxbaselvl]; j++ )
						tiledesc[j].flags |= 1;
				}
			}
		}
	}
	else
		for( j = 0; j < patchidx[maxbaselvl]; j++ )
			tiledesc[j].flags |= 1;	
}

//========================================================
//					Normal textures
//========================================================

void TerrainManager::LoadNormalMap() {

	if( !CFG->bNormalMap )	return;

	UINT j, i;
	char fname[256], cpath[256];

	//L1-L8 normal map
	sprintf( fname, "%s_norm.tex", objname );
	if( gc->TexturePath( fname, cpath ) ) {
		nnorm = patchidx[maxbaselvl];
		normbuf = new ID3D11ShaderResourceView *[nnorm];
		memset( normbuf, 0, sizeof(ID3D11ShaderResourceView*)*nnorm );

		if( nnorm = LoadTextures( cpath, nnorm, normbuf ) ) {
			while( nnorm > patchidx[maxbaselvl] )	REL( normbuf[--nnorm] );
			for( j = 0; j < patchidx[maxbaselvl]; j++ )
				tiledesc[j].ntex.reset(new CachedTexture(normbuf[j]));
		}
		else {
			delete [ ] normbuf;
			normbuf = NULL;
		}
	}
		
	if( maxlvl > 8 ) {
		FILE *file;
		sprintf( fname, "%s_tile_norm.bin", objname );
		if( gc->TexturePath( fname, cpath ) && (file = fopen( cpath, "rb" ) ) ) {
			char id[9];

			memset( id, 0, 9 );
			fread( id, 1, 8, file );
			DWORD tilever = 10;
			if( !strncmp( id, "PLTS", 4 ) )
				tilever = 1;
			else {
				tilever = 0;
				fseek( file, 0, SEEK_SET );
			}
			DWORD n;
			fread( &n, 4, 1, file );
			TILEFILESPEC *tfs = new TILEFILESPEC [n];
			fread( tfs, 32, n, file );
			fclose( file );

//			memset( L, 0, 16 );
//			memset( E, 0, 16 );

			TILEDESC1 *tile8 = tiledesc + patchidx[7];//first L8 tile
			for( j = 0; j < 364; j++ ) {
				TILEDESC1 &tile8j = tile8[j];
				for( i = 0; i < 4; i++ )
					if( tfs[j].subidx[i] )
						AddSubTileNormalData( tile8j, tfs, j, i, 9, tilever );
			}

			delete [ ] tfs;
		}
	}
}

void TerrainManager::AddSubTileNormalData( TILEDESC1 &td, TILEFILESPEC *tfs, DWORD idx, DWORD sub, DWORD level, DWORD tilever ) {
	DWORD j, subidx = tfs[idx].subidx[sub];
	TILEFILESPEC &t = tfs[subidx];
	bool bSubTiles = false;

	for( j = 0; j < 4; j++ ) {
		if( t.subidx[j] ) {
			bSubTiles = true;
			break;
		}
	}

	if( t.flags || bSubTiles ) {
		if( level <= maxlvl ) {

			//create if tile is not created yet
			if( !td.subtile[sub] ) {
				td.subtile[sub] = NewTile( 0.0f, 0.0f );
				td.subtile[sub]->flags |= RECOMPUTEBS;
				if( !tilever )	td.subtile[sub]->flags |= OLDIDX;	//"old style index"
			}

			//load flags and normal texture offset
			if( t.sidx != NOTILE ) {
				td.subtile[sub]->flags |= LOAD_NORM;
				td.subtile[sub]->ntex.reset(new CachedTexture(td.subtile[sub], LOAD_NORM, t.sidx));
			}
			
			if( bSubTiles )
				for( j = 0; j < 4; j++ )
					if( t.subidx[j] )
						AddSubTileNormalData( *td.subtile[sub], tfs, subidx, j, level+1, tilever );

			if( t.sidx != NOTILE )	nhinorm++;
		}
	//	else	td.subtile[sub] = NULL;	//maximum level reached
	}
}

//========================================================
//					Heightmaps
//========================================================

void TerrainManager::LoadHeightMap() {

	if( !CFG->bHeightMap )	return;

	FILE *file;
	DWORD npatch;
	char fname[256], cpath[256];

	sprintf( fname, "%s_height.bin", objname );
	if( gc->TexturePath( fname, cpath ) && (file = fopen( cpath, "rb" )) ) {

//		bHasHeightmapFile = true;
//		bL5Generated = true;

		L8HeightMapGenerated = true;

		char id[9] = "        ";
		fread( id, 1, 8, file );	//"D11_0064"

		fread( &HeightScale, 4, 1, file );

		fread( &npatch, 4, 1, file );
		HMAPFILESPEC *hfs = new HMAPFILESPEC [npatch];
		int size = sizeof(HMAPFILESPEC);
		fread( hfs, sizeof(HMAPFILESPEC), npatch, file );
		fclose( file );

		sprintf( fname, "%s_height.tex", objname );
		if( gc->TexturePath( fname, cpath ) ) {

			LoadL8HeightData( cpath, hfs );

			TILEDESC1 *td8 = tiledesc + patchidx[7];
			for( UINT j = 0; j < 364; j++ ) {
				TILEDESC1 &tile8j = td8[j];
				for( UINT i = 0; i < 4; i++ )
					if( hfs[ hfs[j].subidx[i] ].hidx != NOTILE64 )
						AddSubTileHeightData( tile8j, hfs, j, i, 9 );
			}

			delete [ ] hfs;
		}
	}
}

void TerrainManager::AddSubTileHeightData( TILEDESC1 &td, HMAPFILESPEC *hfs, DWORD idx, DWORD sub, DWORD level ) {
	DWORD j, subidx = hfs[idx].subidx[sub];
	HMAPFILESPEC &h = hfs[subidx];
	bool bSubTiles = false;

	for( j = 0; j < 4; j++ ) {
		if( hfs[ h.subidx[j] ].hidx != NOTILE64 ) {
			bSubTiles = true;
			break;
		}
	}

	if( h.flags || bSubTiles ) {
		if( level <= maxlvl ) {
			if( h.hidx == NOTILE64 )		return;

			//create if tile is not created yet
			if( !td.subtile[sub] ) {
				td.subtile[sub] = NewTile( 0.0f, 0.0f );
				td.subtile[sub]->flags |= RECOMPUTEBS;
			}

			//load flags and height data block offset
			td.subtile[sub]->flags |= LOAD_HEIGHT;
			td.subtile[sub]->htex.reset(new CachedTexture(td.subtile[sub], LOAD_HEIGHT, (UINT32)h.hidx));

			//load height data for subtiles if they exist
			if( bSubTiles )
				for( j = 0; j < 4; j++ )
					if( h.subidx[j] && hfs[ h.subidx[j] ].hidx != NOTILE64 )
						AddSubTileHeightData( *td.subtile[sub], hfs, subidx, j, level+1 );

			//increment counter
			nhiheight++;
		}
	}
}

//========================================================
//					Land cover maps
//========================================================

void TerrainManager::LoadLandCoverMap() {

//	if( !CFG->bLandCoverMap || !CFG->bTerrainDetails_Average )	return;

	FILE *file;
	DWORD npatch;
	char fname[256], cpath[256];

	sprintf( fname, "%s_lcover.bin", objname );
	if( gc->TexturePath( fname, cpath ) && (file = fopen( cpath, "rb" )) ) {

		char id[9] = "        ";
		fread( id, 1, 8, file );
		fread( &npatch, 4, 1, file );
		LCOVERFILESPEC *lfs = new LCOVERFILESPEC [npatch];
		fread( lfs, sizeof(LCOVERFILESPEC), npatch, file );
		fclose( file );

		sprintf( fname, "%s_lcover.tex", objname );
		if( gc->TexturePath( fname, cpath ) ) {

			LoadL8LandCoverData( cpath, lfs );

			TILEDESC1 *td8 = tiledesc + patchidx[7];
			for( UINT j = 0; j < 364; j++ ) {
				TILEDESC1 &tile8j = td8[j];
				for( UINT i = 0; i < 4; i++ )
					if( lfs[ lfs[j].subidx[i] ].lidx != NOTILE )
						AddSubTileLandCoverData( tile8j, lfs, j, i, 9 );
			}

			delete [ ] lfs;
		}
	}
	else {
		CFG->bLandCoverMap = false;
//		CFG->bForestMicroTextures = false;
		CFG->bTerrainDetails_Average = false;
		CFG->bTerrainDetails_High = false;
	}
}

void TerrainManager::AddSubTileLandCoverData( TILEDESC1 &td, LCOVERFILESPEC *lfs, DWORD idx, DWORD sub, DWORD level ) {
	DWORD j, subidx = lfs[idx].subidx[sub];
	LCOVERFILESPEC &l = lfs[subidx];
	bool bSubTiles = false;

	for( j = 0; j < 4; j++ ) {
		if( l.subidx[j] ) {
			bSubTiles = true;
			break;
		}
	}

	if( l.flags || bSubTiles ) {
		if( level <= maxlvl ) {
			if( l.lidx == NOTILE )	return;

			//create if tile is not created yet
			if( !td.subtile[sub] ) {
				td.subtile[sub] = NewTile( 0.0f, 0.0f );
			//	td.subtile[sub]->flags |= RECOMPUTEBS;
			}

			//load flags and LCC texture offset
			td.subtile[sub]->flags |= LOAD_LCOVER;
			td.subtile[sub]->ctex = (ID3D11ShaderResourceView*)((UINT32)l.lidx);

			//load LCC data for subtiles if they exist
			if( bSubTiles )
				for( j = 0; j < 4; j++ )
					if( l.subidx[j] && lfs[l.subidx[j] ].lidx != NOTILE )
						AddSubTileLandCoverData( *td.subtile[sub], lfs, subidx, j, level+1 );

			//increment counter
			nhilcover++;
		}
	}
}

#pragma endregion

//=============================================================================
//				Loads L1-L8 diffuse, specular and normal textures
//=============================================================================

#pragma region base level resource laoding

DWORD TerrainManager::LoadTextures( char *fname, DWORD ntex, ID3D11ShaderResourceView **out ) {
	DWORD loaded = 0;
	HRESULT hr;
	FILE *file;
	D3DX11_IMAGE_INFO IInfo;
	D3DX11_IMAGE_LOAD_INFO LInfo;

	if( !(file = fopen( fname, "rb" )) )
		return 0;

	fseek( file, 0, SEEK_END );
	DWORD fsize = ftell( file );
	DWORD byteleft = fsize;
	BYTE *location, *buffer = new BYTE [fsize+1];
	rewind( file );
	fread( buffer, 1, fsize, file );
	fclose( file );

	location = buffer;
	while( ntex > loaded && byteleft > 0 ) {
		if( *(DWORD*)location != MAKEFOURCC( 'D', 'D', 'S', ' ' ) ) {
			delete [ ] buffer;
			return loaded;
		}

		DDSURFACEDESC2 *DDSdesc = (DDSURFACEDESC2*)(location+4);
		DWORD esize = DDSdesc->dwLinearSize;
		esize += (4 + sizeof(DDSURFACEDESC2));

		D3DX11GetImageInfoFromMemory( location, esize, NULL, &IInfo, &hr );
		if( hr != S_OK ) {
			location += esize;
			byteleft -= esize;
			continue;
		}

		LInfo.BindFlags = D3D11_BIND_SHADER_RESOURCE;
		LInfo.CpuAccessFlags = 0;
		LInfo.Depth = 1;
		LInfo.Filter = D3DX11_FILTER_NONE;
		LInfo.FirstMipLevel = 0;
		LInfo.Format = IInfo.Format;
		LInfo.MipFilter = D3DX11_FILTER_NONE;
		LInfo.MipLevels = IInfo.MipLevels;
		LInfo.MiscFlags = 0;
		LInfo.pSrcInfo = &IInfo;
		LInfo.Usage = D3D11_USAGE_IMMUTABLE;
		LInfo.Width = IInfo.Width;
		LInfo.Height = IInfo.Height;

		D3DX11CreateShaderResourceViewFromMemory( Dev, location, esize, &LInfo, NULL, &out[loaded], &hr );

		location += esize;
		byteleft -= esize;
		if( hr == S_OK )
		{
#if _DEBUG
			{	
				char buffer[200];
				buffer[199] = '\0';
				sprintf(buffer, "[TerrainManager::LoadTextures]%d-%s", ntex, fname);
				if (out[loaded])
					out[loaded]->SetPrivateData(WKPDID_D3DDebugObjectName, 200, buffer);
			}
#endif
			loaded++;
		}
	}
	delete [ ] buffer;
	return loaded;
}

//=============================================================================
//				Loads L8 heightmap and apply heightmap gen. module
//=============================================================================

DWORD TerrainManager::LoadL8HeightData( char *fname, HMAPFILESPEC *hfs ) {

	FILE *file;

	if( !(file = fopen( fname, "rb" )) )	return 0;

	UINT ntex = 0;
	INT16 int_data[16641], *rows[129];
	ID3D11Texture2D *tex;
	D3D11_TEXTURE2D_DESC tdesc;
	D3D11_SUBRESOURCE_DATA sdata;
	D3D11_SHADER_RESOURCE_VIEW_DESC srvdesc;

	heightbuf = new ID3D11ShaderResourceView *[364];
	memset( heightbuf, 0, sizeof(ID3D11ShaderResourceView*)*364 );
	nheight = 364;

	for( UINT j = 0; j < 364; j++ ) {

		if( hfs[j].hidx == NOTILE64 )	continue;

		//===============================================
		//				read PNG image
		//===============================================

		TILEDESC1 &td = tiledesc[patchidx[7] + j];
		_fseeki64( file, hfs[j].hidx, SEEK_SET );

		png_structp png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, NULL, NULL, NULL );
		png_infop info_ptr = png_create_info_struct( png_ptr );
		png_set_read_fn( png_ptr, file, read_callback );
		png_read_info( png_ptr, info_ptr );

		for( UINT i = 0; i < 129; i++ )		rows[i] = &int_data[i*129];
		png_read_image( png_ptr, (png_bytepp)rows );

		png_destroy_info_struct( png_ptr, &info_ptr );
		png_destroy_read_struct( &png_ptr, &info_ptr, NULL );
		
		td.HMAP = new float [16641];
		td.amin = 32760.0f;
		td.amax = -32760.0f;
		for( UINT i = 0; i < 16641; i++ ) {
			td.HMAP[i] = (float)int_data[i];
			if( td.HMAP[i] > td.amax )	td.amax = td.HMAP[i];
			if( td.HMAP[i] < td.amin )	td.amin = td.HMAP[i];
		}

		//===============================================
		//		   add noiselib noise function
		//===============================================

		//... too low resolution - heightmap is enough [?]

		//===============================================
		//				 create resource
		//===============================================

		memset( &tdesc, 0, sizeof(tdesc) );
		tdesc.ArraySize = 1;
		tdesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
		tdesc.CPUAccessFlags = 0;
		tdesc.Format = DXGI_FORMAT_R32_FLOAT;
		tdesc.Height = 129;
		tdesc.Width = 129;
		tdesc.MipLevels = 1;
		tdesc.MiscFlags = 0;
		tdesc.SampleDesc.Count = 1;
		tdesc.Usage = D3D11_USAGE_IMMUTABLE;
	
		memset( &sdata, 0, sizeof(sdata) );
		sdata.pSysMem = td.HMAP;
		sdata.SysMemPitch = 516;

		HR( Dev->CreateTexture2D( &tdesc, &sdata, &tex ) );

		memset( &srvdesc, 0, sizeof(srvdesc) );
		srvdesc.Format = DXGI_FORMAT_R32_FLOAT;
		srvdesc.Texture2D.MipLevels = 1;
		srvdesc.Texture2D.MostDetailedMip = 0;
		srvdesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;

		HR( Dev->CreateShaderResourceView( tex, &srvdesc, &heightbuf[j]/*td.htex*/ ) );
		td.htex.reset(new CachedTexture(heightbuf[j]));
#if _DEBUG
	{
		char buffer[200];
		buffer[199] = '\0';
		sprintf(buffer, "[LoadL8HeightData]%s", fname);
		if (tex)
			tex->SetPrivateData(WKPDID_D3DDebugObjectName, 200, buffer);
		if (td.htex->GetTexture())
			(*td.htex->GetTexture())->SetPrivateData(WKPDID_D3DDebugObjectName, 200, buffer);
	}
#endif

		REL( tex );

		//heightbuf[j] = td.htex;
		ntex++;
	}

	return ntex;
}

DWORD TerrainManager::LoadL8LandCoverData( char *fname, LCOVERFILESPEC *lfs ) {

	FILE *file;
	if( !(file = fopen( fname, "rb" )) )	return 0;

	UINT ntex = 0;
	UINT8 data[16384], *rows[128];
	ID3D11Texture2D *tex;
	D3D11_TEXTURE2D_DESC tdesc;
	D3D11_SUBRESOURCE_DATA sdata;
	D3D11_SHADER_RESOURCE_VIEW_DESC srvdesc;

	lcoverbuf = new ID3D11ShaderResourceView *[364];
	memset( lcoverbuf, 0, sizeof(ID3D11ShaderResourceView*)*364 );
	nlcover = 364;

	for( UINT j = 0; j < 364; j++ ) {

		if( lfs[j].lidx == NOTILE )	continue;

		//===============================================
		//				read PNG image
		//===============================================

		TILEDESC1 &td = tiledesc[patchidx[7] + j];
		fseek( file, lfs[j].lidx, SEEK_SET );

		png_structp png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, NULL, NULL, NULL );
		png_infop info_ptr = png_create_info_struct( png_ptr );
		png_set_read_fn( png_ptr, file, read_callback );				
		png_read_info( png_ptr, info_ptr );

		for( UINT i = 0; i < 128; i++ )		rows[i] = &data[i*128];
		png_read_image( png_ptr, (png_bytepp)rows );

		png_destroy_info_struct( png_ptr, &info_ptr );
		png_destroy_read_struct( &png_ptr, &info_ptr, NULL );

		//===============================================
		//				 create resource
		//===============================================

		memset( &tdesc, 0, sizeof(tdesc) );
		tdesc.ArraySize = 1;
		tdesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
		tdesc.CPUAccessFlags = 0;
		tdesc.Format = DXGI_FORMAT_R8_UINT;
		tdesc.Height = 128;
		tdesc.Width = 128;
		tdesc.MipLevels = 1;
		tdesc.MiscFlags = 0;
		tdesc.SampleDesc.Count = 1;
		tdesc.Usage = D3D11_USAGE_IMMUTABLE;
	
		memset( &sdata, 0, sizeof(sdata) );
		sdata.pSysMem = data;
		sdata.SysMemPitch = 128;

		HR( Dev->CreateTexture2D( &tdesc, &sdata, &tex ) );

		memset( &srvdesc, 0, sizeof(srvdesc) );
		srvdesc.Format = DXGI_FORMAT_R8_UINT;
		srvdesc.Texture2D.MipLevels = 1;
		srvdesc.Texture2D.MostDetailedMip = 0;
		srvdesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;

		HR( Dev->CreateShaderResourceView( tex, &srvdesc, &td.ctex ) );
#if _DEBUG
	{
		char buffer[200];
		buffer[199] = '\0';
		sprintf(buffer, "[LoadL8LandCoverData]%s", fname);
		if (tex)
			tex->SetPrivateData(WKPDID_D3DDebugObjectName, 200, buffer);
		if (td.ctex)
			td.ctex->SetPrivateData(WKPDID_D3DDebugObjectName, 200, buffer);
	}
#endif
		REL( tex );

		lcoverbuf[j] = td.ctex;
		ntex++;
	}

	return ntex;
}

void TerrainManager::SetWaterMicroTexture( const char *fname ) {
	rend->SetWaterMicroTexture( fname );
}

void TerrainManager::UnloadTextures()
{
	for(UINT i = 0; i < buf.size(); i++)
	{
		TILEDESC1* td = buf[i];
		if (td)
		{
			if (td->tex)
				td->tex->Unload();
			if (td->tex_old)
				td->tex_old->Unload();
			if (td->ntex)
				td->ntex->Unload();
			if (td->htex)
				td->htex->Unload();
		}
		//td->tex_old->Unload();
	}
}

#pragma endregion

//============================================================================
//								Static
//============================================================================

TileLoader			*TerrainManager::tload			= NULL;

D3DXMATRIX
	TerrainManager::RSouth,
	TerrainManager::mWorld_north;
MATRIX3
	TerrainManager::grot_north;
TILERENDERSPEC
	*TerrainManager::TileList = NULL,
	*TerrainManager::DetailsTileList = NULL;
UINT
	TerrainManager::ntile = 0,
	TerrainManager::tbufsize = 0,
	TerrainManager::ntile_det = 0,
	TerrainManager::tbufsize_det = 0;

UINT TerrainManager::patchidx[9] = { 0, 1, 2, 3, 5, 13, 37, 137, 501 };
UINT TerrainManager::NLAT[9] = { 0, 1, 1, 1, 1, 1, 2, 4, 8 };
UINT
	TerrainManager::NLNG5[1] = { 4 },
	TerrainManager::NLNG6[2] = { 8, 4 },
	TerrainManager::NLNG7[4] = { 16, 16, 12, 6 },
	TerrainManager::NLNG8[8] = { 32, 32, 30, 28, 24, 18, 12, 6 },
	*TerrainManager::NLNG[9] = { NULL, NULL, NULL, NULL, NULL, NLNG5, NLNG6, NLNG7, NLNG8 };

void TerrainManager::GlobalInit() {

	ID3D11Buffer *CB_AtmParams, *CB_PlanetShadowParams;

	TileRenderMgr::GlobalInit( &CB_AtmParams, &CB_PlanetShadowParams );
//	DetailsManager::GlobalInit( CB_AtmParams, CB_PlanetShadowParams );

	//TRS buffer for tiles of constant resoltion, that will be used in DetailsManager
	ntile = 0;
	tbufsize = 32;
	TileList = new TILERENDERSPEC [tbufsize];
	memset( TileList, 0, sizeof(TILERENDERSPEC)*tbufsize );

	tbufsize_det = 32;
	DetailsTileList = new TILERENDERSPEC [tbufsize_det];
	memset( DetailsTileList, 0, sizeof(TILERENDERSPEC)*tbufsize_det );

	D3DXMatrixRotationX( &RSouth, (float)PI );
	
	tload = new TileLoader();
}

void TerrainManager::GlobalExit() {

	TileRenderMgr::GlobalExit();
	DetailsManager::GlobalExit();

	if( TileList )			delete [ ] TileList;
	if( DetailsTileList )	delete [ ] DetailsTileList;
	delete tload;
}
